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

我正在查看来自不安全编程的示例abo3.c ,并且我没有在下面的示例中讨论转换。 有人可以开导我吗?

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); } 

那么 – 系统和投放的铸造是什么? 他们都返回一个int所以为什么把它变成无效?

我非常感谢对整个计划的解释。

[编辑]
谢谢你们的投入!

Jonathan Leffler ,实际上代码有“坏”的原因。 它应该是可利用的,溢出缓冲区和函数指针等.mishou.org有一篇关于如何利用上述代码的博客文章。 其中很多仍然在我头上。

bta ,我从上面的博客文章中得知,转换系统会以某种方式阻止链接器删除它。

有一点不能立即明确的是系统和放置地址都写在同一个位置,我想这可能是gera所说的“因此链接器不会删除它”。

虽然我们讨论的是函数指针的主题,但现在我想问一个后续问题,即语法更清晰。 我正在使用函数指针查看一些更高级的示例,偶然发现这个令人厌恶的例子,取自托管shellcode的站点。

 #include 

 char shellcode [] =“some shellcode”;

 int main(void)
 {
     fprintf(stdout,“长度:%d \ n”,strlen(shellcode));
     (*(void(*)())shellcode)();
 }

所以数组被强制转换为返回void的函数,引用并调用? 这看起来很讨厌 – 所以上面代码的目的是什么?

[/编辑]

原始问题

用户bta给出了演员的正确解释 – 并评论了铸造system的不正确性。

我要补充一下:

extern线条充其量是奇怪的。 在严格的C99下是错误的,因为没有类型,这使得它无效; 在C89下,类型将被假定为int 。 该行说’有一个外部定义的整数称为系统,另一个称为puts’,这是不正确的 – 有一对带有这些名称的函数。 代码实际上可能“有效”,因为链接器可能将函数与假定的整数相关联。 但对于64位机器而言,指针与int大小不同是不安全的。 当然,代码应包含正确的头文件(对于system()exit() puts() ,以及strcpy() )。

exit(1); 在两个单独的计数上是不好的。

  • 它表示失败 – 无条件。 您以0或EXIT_SUCCESS退出以指示成功。

  • 在我看来,最好在main()的末尾使用return而不是exit() 。 不是每个人都必须同意我,但我不希望看到exit()作为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' 

不好的代码! 我担心包含此类代码的信息来源并不能解释所有可怕性(因为显示此类混乱代码的唯一借口是剖析并更正它)。


代码中还有另一个奇怪之处:

 int main(int argv,char **argc) 

这是’正确’(它会起作用),但100%是常规的。 正常的声明是:

 int main(int argc, char **argv) 

名称是’参数计数’和’参数向量’的缩写,并且使用argc作为字符串的向量(数组)的名称是exception的并且完全令人困惑。


访问过引用的站点后,您可以看到它正在通过一组分级示例。 我不确定作者是否只是对argc / argv问题有一个盲点,或者是故意搞乱( ‘abo1’表示他正在玩,但在我看来这没有帮助)。 这些例子可以满足你的想法,但对他们所做的事情没有多少解释。 我认为我不能推荐这个网站。


扩展问题

这段代码中的演员是做什么的?

 #include  char shellcode[] = "some shellcode"; int main(void) { fprintf(stdout,"Length: %d\n",strlen(shellcode)); (*(void(*)()) shellcode)(); } 

这将获取字符串’shellcode’的地址,并将其视为指向函数的指针,该函数接受一组不确定的参数并且不返回任何值并在没有参数的情况下执行它。 该字符串包含用于某些漏洞利用的二进制汇编程序代码 – 通常运行s​​hell – 入侵者的目标是获取root权限程序来执行其shellcode并为其提供具有root权限的命令提示符。 从那里,系统是他们拥有的。 对于练习,第一步是获得一个非root程序来执行shellcode,当然。

回顾分析

Mishou网站上的分析并不像我想的那样具有权威性:

一,此代码使用C语言中的extern关键字来使系统和函数可用。 这样做(我认为)基本上是直接引用(隐含的)头文件中定义的函数的位置…我得到的印象是GDB自动神奇地包括用于系统的头文件stdlib.h和用于puts的stdio.h 。 有一点不能立即明确的是系统和放置地址都写在同一个位置,我想这可能是gera所说的“因此链接器不会删除它”。

剖析评论:

  1. 第一句话不是很准确; 它告诉编译器符号systemputs在其他地方被定义(作为整数)。 链接代码时, puts() -the-function的地址是已知的; 代码将它视为整数变量,但整数变量的地址实际上是函数的地址 – 因此强制转换会强制编译器将其视为函数指针。
  2. 第二句不完全准确; 链接器通过函数符号system()puts()解析外部“变量”的地址。
  3. GDB没有做任何编译或链接过程。
  4. 最后一句话根本没有任何意义。 地址只会写入同一位置,因为您具有初始化和对同一变量的赋值。

这并没有激励我阅读整篇文章,必须说。 尽职调查迫使我前进; 之后的解释更好,但仍然没有我想象的那么清楚。 但是,使用超长但精心设计的参数字符串溢出缓冲区的操作是该操作的核心。 代码提到puts()system()这样当在非漏洞利用模式下运行时, puts()函数是一个已知符号(否则,你必须使用dlopen()来查找它的地址),所以当在漏洞利用模式下运行时,代码具有可供直接使用的符号system() 。 可执行文件中没有未使用的外部引用 – 当您意识到典型系统头中有多少符号与包含头文件的程序所使用的编号相比时,这是一件好事。

显示了一些巧妙的技巧 – 尽管特定页面上没有显示这些技巧的实现; 我假设(未经validation) getenvaddr程序的信息可用。

abo3.c代码可以写成:

 #include  #include  #include  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); } 

现在它只使用我最初使用的繁琐的编译选项编译了一个警告 – 这就是没有使用’argc’的准确警告。 它和原版一样可以利用; 它是“更好”的代码,因为它编译得很干净。 间接是不必要的神秘,而不是使代码可利用的关键部分。

systemputs通常都返回int 。 代码将它们转换为返回void的指针,可能是因为他们想要忽略返回的任何值。 这应该等同于使用(void)fn(argc[2]); 如果演员没有改变返回类型,则为倒数第二行。 有时会为回调函数抛弃返回类型,而这段代码片段似乎是回调的简单示例。

如果从未使用system的演员阵容,那么为什么呢? 我假设这里没有显示更多的代码。