Created
February 23, 2023 14:06
-
-
Save dixyes/558e31c2129607cfcba70a81d65f8c52 to your computer and use it in GitHub Desktop.
How to write hello world in PHP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// x86_64 sysv abi only | |
// simple hello | |
$code = "" . | |
"\x48\xc7\xc0\x01\x00\x00\x00" . // mov $0x1 (SYS_write for x86_64), %rax | |
"\x48\xc7\xc7\x01\x00\x00\x00" . // mov $0x1 (stdout), %rdi | |
"\x48\x8d\x35\x0b\x00\x00\x00" . // lea 0xb(%rip), %rsi PIE! | |
"\x48\xc7\xc2\x0d\x00\x00\x00" . // mov $0xd (sizeof("hello world!\n") - 1), %rdx | |
"\x0f\x05" . // syscall | |
"\xc3" . // ret | |
"\x90" . // nop | |
"hello world!\n" . // .ascii "hello world\n" | |
""; | |
// more complex hello | |
<<<'C' | |
void zif_hello(zend_execute_data *ed, zval* ret) | |
{ | |
uint64_t arg; | |
if (zend_parse_parameters(1, "l", &arg)) { | |
return; | |
}; | |
/* | |
this is | |
ret->value.long = arg * 7; | |
ret->u2.type = IS_LONG; | |
*/ | |
RETVAL_LONG(arg * 7); | |
printf("hello! answer is %d\n", arg); | |
} | |
C; | |
$code = "" . | |
// prepare stack: | |
"\x53" . // push %rbx | |
"\x48\x89\xf3" . // mov %rsi, %rbx | |
"\x48\x83\xec\x10" . // sub $0x10, %rsp | |
// then zend_parse_parameters(1, "l", &arg); : | |
// prepare retval | |
"\x31\xc0" . // xor %eax, %eax (eax is the return val) | |
// prepare calling args for zend_parse_parameters | |
"\xbf\x01\x00\x00\x00" . // mov $0x1, %edi (0x1 is the 1st arg) | |
"\x48\x8d\x35\x5f\x00\x00\x00" . // lea l(%rip), %rsi ("l", the 2nd arg) | |
"\x48\x8d\x54\x24\x08" . // lea 0x8(%rsp),%rdx (&input is the 3rd arg) | |
// call it | |
"\xff\x15\x56\x00\x00\x00" . // call *zend_parse_parameters(%rip) | |
// check the result | |
"\x85\xc0" . // test %eax, %eax | |
"\x75\x27" . // jne 4c | |
// first param is now 0x8(%rsp) | |
"\x48\x8b\x54\x24\x08" . // mov 0x8(%rsp), %rdx | |
// use lea to caculate arg1*8 | |
"\x48\x8d\x34\xd5\x00\x00\x00\x00" . // lea 0x0(,%rdx,8), %rsi | |
// calc arg1*8-arg1 | |
"\x48\x29\xd6" . // sub %rdx, %rsi | |
// ret->value = arg1*7 | |
"\x48\x89\x33" . // mov %rsi, (%rbx) | |
// rer->type = 4 (IS_LONG) | |
"\xc7\x43\x08\x04\x00\x00\x00" . // movl $4, 8(%rbx) | |
// call printf | |
"\x48\x8d\x3d\x1a\x00\x00\x00" . // lea hello(%rip), %rdi | |
"\xff\x15\x33\x00\x00\x00" . // call *printf(%rip) | |
// restore the stack | |
"\x48\x83\xc4\x10" . // add $0x10, %rsp | |
"\x5b" . // pop %rbx | |
// return | |
"\xc3" . // ret | |
"\x90\x90\x90\x90\x90\x90\x90\x90" . // nop for align | |
"\x90\x90\x90\x90\x90\x90" . // nop for align | |
// "data" segment | |
"hello! answer is %d\n\0" . // .ascii | |
"l\0". // .asciz "l" | |
""; | |
file_put_contents("ceshi2.bin", $code); | |
$ffi = FFI::cdef(<<<C | |
typedef uint64_t size_t; | |
typedef int64_t off_t; | |
/* not used, so only empty struct */ | |
typedef struct _zend_class_entry {} zend_class_entry; | |
typedef struct _zend_execute_data {} zend_execute_data; | |
typedef struct _zend_array {} HashTable; | |
/* fake */ | |
typedef char* zval; | |
/* used */ | |
typedef struct { | |
void *ptr; | |
uint32_t type_mask; | |
} zend_type; | |
typedef void (*zif_handler)(zend_execute_data *execute_data, zval *return_value); | |
typedef struct _zend_internal_arg_info { | |
const char *name; | |
zend_type type; | |
const char *default_value; | |
} zend_internal_arg_info; | |
typedef struct _zend_function_entry { | |
const char *fname; | |
zif_handler handler; | |
const struct _zend_internal_arg_info *arg_info; | |
uint32_t num_args; | |
uint32_t flags; | |
} zend_function_entry; | |
/* sys/mman.h */ | |
void *mmap (void *__addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset); | |
int mprotect (void *__addr, size_t __len, int __prot); | |
/* zend_API.h */ | |
int zend_register_functions(zend_class_entry *scope, const zend_function_entry *functions, HashTable *function_table, int type); | |
int zend_parse_parameters(uint32_t num_args, const char *type_spec, ...); | |
/* errno.h */ | |
int errno; | |
/* string.h */ | |
char *strerror (int __errnum); | |
/* stdlib.h */ | |
void *malloc (size_t __size); | |
/* stdio.h */ | |
int printf (const char *__restrict __fmt, ...); | |
C); | |
// create a buffer for text segment use | |
$buf = $ffi->mmap(null, 4096, 0x2 /* PROT_WRITE */, 0x20 /* MAP_ANONYMOUS */ | 0x02 /* MAP_PRIVATE */, 0, 0); | |
if (0 === FFI::memcmp("\xff\xff\xff\xff\xff\xff\xff\xff" /* int64 val of -1 */, FFI::addr($buf), 8)) { | |
// ret == MAP_FAILED ((void *) -1) | |
printf("mmap failed: %s\n", FFI::string($ffi->strerror($ffi->errno))); | |
exit(1); | |
} | |
// add function addresses | |
$code .= FFI::string(FFI::cast("char[8]",$ffi->zend_parse_parameters), 8); | |
$code .= FFI::string(FFI::cast("char[8]",$ffi->printf), 8); | |
// make text segment | |
FFI::memcpy($buf, $code, strlen($code)); | |
// make it executable | |
$ret = $ffi->mprotect($buf, 4096, 0x1 /* PROT_READ */ | 0x4 /* PROT_EXEC */); | |
if (0 !== $ret) { | |
// ret == MAP_FAILED ((void *) -1) | |
printf("mprotect failed: %s\n", FFI::string($ffi->strerror($ffi->errno))); | |
exit(1); | |
} | |
// try run it | |
// $ffi->cast("zif_handler", $buf)(null, FFI::addr(FFI::new("char[4096]"))); | |
// make function name | |
$fname = $ffi->new("char[6]", false, true); | |
FFI::memcpy($fname, "hello\0", 6); | |
// make arg info | |
// first, we begin with these macros | |
// this will tell php hello needs 1 arg, returns IS_LONG | |
<<<'C' | |
#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, is_tentative_return_type) \ | |
static const zend_internal_arg_info name[] = { \ | |
{ (const char*)(uintptr_t)(required_num_args), \ | |
ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), \ | |
NULL }, | |
#define _ZEND_ARG_INFO_FLAGS(pass_by_ref, is_variadic, is_tentative) \ | |
(((pass_by_ref) << _ZEND_SEND_MODE_SHIFT) | ((is_variadic) ? _ZEND_IS_VARIADIC_BIT : 0) | ((is_tentative) ? _ZEND_IS_TENTATIVE_BIT : 0)) | |
C; | |
$required_args = FFI::new("uintptr_t", false, true); | |
$required_args->cdata = 1; | |
$arg_info = $ffi->new("zend_internal_arg_info[2]", false, true); | |
$arg_info[0]->name = FFI::cast("char*", $required_args); | |
// to make a zend_type: | |
<<<'C' | |
#define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ | |
ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ( (code) == IS_ITERABLE ? _ZEND_TYPE_ITERABLE_BIT : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code))))) \ | |
| ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)) | |
#define ZEND_TYPE_INIT_MASK(_type_mask) \ | |
{ NULL, (_type_mask) } | |
C; | |
// so make its ptr null | |
$arg_info[0]->type->ptr = null; | |
// IS_LONG is not bool, not iterable, not mixed, so it's 1 << IS_LONG | |
// we donot allow null, so |= 0 | |
// we donot have extra flags, so |= 0 | |
$arg_info[0]->type->type_mask = 1 << 4/* IS_LONG */; | |
$arg_info[0]->default_value = null; | |
// then we make the first arg_info for hello function: | |
$first_arg_name = FFI::new("char[6]", false, true); | |
FFI::memcpy($first_arg_name, "input\0", 6); | |
$arg_info[1]->name = $first_arg_name; | |
$arg_info[1]->type->ptr = null; | |
$arg_info[1]->type->type_mask = 1 << 4/* IS_LONG */; | |
$arg_info[1]->default_value = null; | |
// create fe | |
<<<'C' | |
#define ZEND_FENTRY(zend_name, name, arg_info, flags) | |
{ #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags }, | |
C; | |
$fe = $ffi->new("zend_function_entry[2]", false, true); | |
$fe[0]->fname = $fname; | |
$fe[0]->handler = $ffi->cast("zif_handler", $buf); | |
$fe[0]->arg_info = $arg_info; | |
$fe[0]->num_args = 1; | |
$fe[0]->flags = 0; | |
$fe[1]->fname = null; | |
$fe[1]->handler = null; | |
$fe[1]->arg_info = null; | |
$fe[1]->num_args = 0; | |
$fe[1]->flags = 0; | |
// register this function | |
$ret = $ffi->zend_register_functions( | |
null, // we donot need a scope | |
$fe, | |
null, // use the global function table | |
1, // persistent | |
); | |
if (0 !== $ret) { | |
// ret == MAP_FAILED ((void *) -1) | |
printf("zend_register_functions failed: %s\n", FFI::string($ffi->strerror($ffi->errno))); | |
exit(1); | |
} | |
$r = new ReflectionFunction('hello'); | |
$params = $r->getParameters(); | |
foreach ($params as $param) { | |
var_dump($param->getType()->getName(), $param->getName()); | |
} | |
var_dump($r->getReturnType()->getName()); | |
var_dump(hello(6)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment