'How can I get a string returned from a function executed from a byte array?
I have a working C program that has the simple function that returns a d
character encoded in a byte array.
char foo() {
return 'd';
}
char byte_array[] = {0xb8,0x64,0x00,0x00,0x00,0xc3};
Then, it executes this function from the byte_array
and prints its output.
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
char byte_array[] = {0xb8,0x64,0x00,0x00,0x00,0xc3};
int main() {
void *addr = (void*)((unsigned long)byte_array & ((0UL - 1UL) ^ 0xfff)); /*get memory page*/
int ans = mprotect(addr, 1, PROT_READ|PROT_WRITE|PROT_EXEC); /*set page attributes*/
if (ans) {
perror("mprotect");
exit(EXIT_FAILURE);
}
char (*func)();
func = (char (*)()) byte_array;
char function_return = (char)(*func)();
printf("%c\n", function_return);
return 0;
}
How can I change this code in order to handle functions like the following?
const char* foo() {
return "string";
}
I've tried this way, but it just prints a weird character to the console:
const char* (*func)();
func = (const char* (*)()) byte_array;
const char* function_return = (const char*)(*func)();
Solution 1:[1]
gcc and similar
String literals are stored in the .rodata segment. It is very unlikely your program to have .rodata at the same address as when you compile your "bytecode".
There is no simple workaround as you also cant have this array stored in the .data
segment for exactly the same reason as when you put your data into .rodata
segment.
I have found some workaround I believe:
const char * __attribute__((noinline)) foo(void)
{
code_start:
asm volatile("call get_ip");
asm volatile("get_ip:");
asm volatile("pop %rax");
asm volatile("jmp_start:");
asm volatile("add $str_start, %rax");
asm volatile("sub $get_ip, %rax");
asm volatile("jmp str_end");
str_start:
asm volatile("str_start:");
asm volatile(".string \"Hello world\"");
asm volatile("str_end:");
}
Solution 2:[2]
You have to write the function in asm yourself; a compiler-generated function to return a pointer to a string-literal will put it in .rodata
, not with the machine code bytes of the function.
Keep in mind that the pointer will be to byte inside this array, unless you want to define the contents of the array in an asm source file or with inline asm so you can get the linker to include a reference to a string literal in .rodata
. Then you're just writing a function called byte_array
, and declaring it weird in C (as a char array instead of a function). Which you can totally do.
But usually the only point of having machine-code bytes in an array like this is for them to be self-contained, and position-independent so they'd still work if copied somewhere. Like shellcode, although apparently you're not worried about avoiding 00
bytes like most shellcode would. (Many paths for getting bytes into buffer overflows involve 0-terminated C strings.)
The most straightforward option is an LEA with a RIP-relative addressing mode; an x86-64 feature that makes it trivial to get a nearby (+-2GiB) address into a register in a fully position-independent way.
# AT&T syntax, assemble with as or gcc -c foo.s
# .section .data # or wherever you want to put this. Or just assemble to get the bytes
byte_array:
lea .Lmystring(%rip), %rax # not shellcode safe, forward LEA has zeros
ret
# *not* in a separate section like one would normally use; contiguous with the code
.Lmystring: .asciz "Hello world\n"
You could put this inside an asm("... \n" "...\n");
statement at global scope in your C source and declare extern char byte_array[]
. An assembler doesn't care whether you write ret
or .byte 0xc3
, regardless of what section you're in, and the C compiler certainly doesn't know.
Or you can just assemble it in a .s
by itself and look at the machine code with a disassembler:
$ gcc -c foo.s
$ objdump -drwC -Mintel foo.o
0000000000000000 <byte_array>:
0: 48 8d 05 01 00 00 00 lea rax,[rip+0x1] # 8 <byte_array+0x8>
7: c3 ret
8: 48 rex.W
9: 65 6c gs ins BYTE PTR es:[rdi],dx
b: 6c ins BYTE PTR es:[rdi],dx
c: 6f outs dx,DWORD PTR ds:[rsi]
d: 20 77 6f and BYTE PTR [rdi+0x6f],dh
10: 72 6c jb 7e <byte_array+0x7e>
12: 64 0a 00 or al,BYTE PTR fs:[rax]
# total bytes: 0x15 = 21
The bytes after the c3 ret
are of course just the ASCII codes for the string, but the coding-space for x86 machine code is almost all used; most byte sequences will decode as something. (There are some illegal combinations, though, e.g. some instructions like prefetcht0
require their operand to be memory, not a register, and there are several illegal 1-byte opcodes like the bytes that are aaa
or push ds
in 32-bit mode.)
To make this shellcode safe avoiding 00
, the LEA has to come after the string, so the 4-byte rel32
is negative, so the high bytes are 0xff
not 0x00
. You can jmp
forward over the string, or you could put the string earlier in your payload before a nop slide. Or for your byte array case, instead of calling to the first byte of the array, (char (*)()) (byte_array + 11)
or whatever.
But if you do either of those, it can't end with a 00
so you'd have to store a 0, e.g. after xor-zeroing a register. That means the bytes have to be in write+exec memory, which a normal build won't do, although you're using mprotect anyway. Making the array local to a function (so the bytes get copied to the stack) and compiling with gcc -zexecstack
will also do that: How to get c code to execute hex machine code?
One terminating 00
byte at the end of your shellcode does get copied by strcat
and similar buffer overflows, so we don't need to do anything special to make sure the terminator for the .asciiz
makes it through.
Another thing you can do is lea string+0x20202020(%rip), %rax
/ sub $0x20202020, %rax
to make all the bytes of the LEA non-zero (and printable ASCII.)
call
/pop
shellcode trick
It could save a byte of code-size to use a common shellcode trick of using a call
to push the address of the byte following it (where you put the string), and then pop that.
(@0___________'s answer uses a very cumbersome variation on that where they use call/pop to get RIP, like 32-bit mode where RIP-relative addressing isn't available. Then separately add one absolute address and subtract another, instead of just adding the difference.)
# .section .data
byte_array: # very inefficient, use LEA instead
jmp .Ldo_call # the call has to be backwards to avoid 00 bytes in its rel32, but jmp rel8 can go forward
.Lback: pop %rax
ret
.Ldo_call: call .Lback # pushes string address, jumps backwards
.asciz "Hello world\n"
This is the standard shellcode version, making sure the call goes backwards
This one is bad for performance because it has a call not matched with a ret, and the call
's offset isn't +0 (which is special-cased for return-address prediction for the call
/pop
trick that 32-bit PIC code uses to find its own EIP.)
If you don't care about being shellcode, just call forward over the bytes whose address you want to push.
# .section .data
byte_array:
call 0f # using GAS local label syntax, forward to next 0:
.asciz "Hello world\n"
0: pop %rax
ret
The call rel32
itself will have the last 3 bytes being 0, and the .asciz
contains a 0 byte before the 58 pop rax
/ C3 ret
, as you can see:
0: e8 0d 00 00 00 call 12 <byte_array+0x12>
5: 48 rex.W
... ASCII bytes ...
f: 64 0a 00 or al,BYTE PTR fs:[rax]
12: 58 pop rax
13: c3 ret
# total bytes: 0x14 = 20.
So it's one byte shorter than the LEA version, same was if both were using a forward jmp rel8
to avoid 00 bytes. (Although as I said, for LEA that alone doesn't solve the problem if you want your string to be 0 terminated.)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |