Calling functions inside shellcode

1.6k views Asked by At

I would like to compile small double(...) functions as x64 shellcode. I already have a working program to generate the encoded assembly for simple math operations like a + b / a, where a and b are double parameters for the function.

The generated code is loaded into a executable mmap buffer and can be called later.

I have the following problem: I would like to call math.h functions like sin, exp etc. inside my generated shellcode. Since all call opcodes somehow use 32-bit adresses or relative jumps, i cannot easily use these instructions. Therefore I tried to implement my own call-instruction this way:

lea rax, [rip+0x0] ;load instruction pointer into rax
add rax, <offset-to-return-to>
push rax

moveabs rax, <adress-of-function> ;load the function pointer to rax
jmp rax

This is my code to generate these instructions:

//lea rax, [rip+0x0]
insertAll(code,std::list<uint8_t>{ 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00 });
//add rax, <offset-to-return-to>
insertAll(code,std::list<uint8_t>{ 0x48, 0x83, 0xC0, <offset-to-return-to>});

//push rax
code.push_back(0x50);

//moveabs rax, address-of-function
uint8_t reg = 0; //rax
uint8_t rexWPrefix = 0x48 + (reg > 8);
uint8_t opCode = 0xB8 + (reg % 8);
insertAll(code,std::list<uint8_t>{ rexWPrefix, opCode });
code.insert(code.end(),reinterpret_cast<uint8_t*>(&fabs),reinterpret_cast<uint8_t*>(&fabs) + 8);

//jmp rax
insertAll(code,std::list<uint8_t>{ 0xFF, 0xE0 });

Unfortunately, calling functions this way does not work, the program crashes with a SIGSEGV error. Is there something wrong with my assembly code or encoding of the instruction? What value should <offset-to-return-to> have, to let the function return to the right position?

Disclaimer: I know, that this is not the best way to handle dynamic generation of code... I could just compile a dynamic library and load it with dlsym. This is just a fun way to learn about assembly/shellcodes and should not be taken too seriously :)

1

There are 1 answers

0
tly On

I found three errors. The jmp instruction is not absolute, but afaik RIP-relative. I used push + ret as an alternative because ret jumps to an absolute address lying on the stack.

Additionally, I did not know that it is mandatory for the caller to reserve 4*8 bytes shadow space on the stack. The details can be found here.

Finally, the code to insert the function pointer into the instruction code was wrong. I accidentally inserted the first 8 bytes of the function code instead of the value of the pointer:

code.insert(code.end(),reinterpret_cast<uint8_t*>(&fabs),reinterpret_cast<uint8_t*>(&fabs) + 8);

This is the finished working code:

//add rsp,0x20  --> shadow space of 4*8 bytes
insertAll(code,std::list<uint8_t>{ 0x48, 0x83, 0xC4, 0x20 } );
//lea rax, [rip+0x0]
insertAll(code,std::list<uint8_t>{ 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00 });
//add rax, 18
insertAll(code,std::list<uint8_t>{ 0x48, 0x83, 0xC0, 18 });
//push rax
code.push_back(0x50);

//moveabs rax, address-of-function
uint8_t reg = 0; //rax
uint8_t rexWPrefix = 0x48 + (reg > 8);
uint8_t opCode = 0xB8 + (reg % 8);
insertAll(code,std::list<uint8_t>{ rexWPrefix, opCode });
void* address = reinterpret_cast<void*>(&my_abs);
code.insert(code.end(),reinterpret_cast<uint8_t*>(&address),reinterpret_cast<uint8_t*>(&address) + sizeof(address));

//push rax
code.push_back(0x50);
//retq
code.push_back(0xC3);
//sub rsp,0x20  --> shadow space of 4*8 bytes
insertAll(code,std::list<uint8_t>{ 0x48, 0x83, 0xEC, 0x20 } );