Skip to content

Instantly share code, notes, and snippets.

@dixyes
Created February 23, 2023 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dixyes/558e31c2129607cfcba70a81d65f8c52 to your computer and use it in GitHub Desktop.
Save dixyes/558e31c2129607cfcba70a81d65f8c52 to your computer and use it in GitHub Desktop.
How to write hello world in PHP
<?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