Introduction

I found an open-source project called DooTask online. Based on the features described on the official website, it seemed quite powerful, so I decided to deploy it locally and try it out.

After deployment, I discovered that the professional version could only be used by three people, which seemed too limited! Since it’s developed in PHP based on Laravel, I’m quite familiar with it. Sure enough, I found the license and user quantity limitation logic, but they used PHP’s FFI, which I had never encountered before…

Overview

PHP FFI (Foreign Function Interface) is a feature introduced in PHP 7.4 that allows PHP to directly call and manipulate external C language libraries (or shared libraries from other languages). Through PHP FFI, developers can directly call functions from these external libraries, access structures and global variables, enabling interaction at the system level.

Some key features and advantages include:

  1. Direct Calling: PHP FFI can directly call C functions without the need for intermediate layers or extensions, simplifying the interaction process with C language libraries.

  2. System-Level Interaction: Through PHP FFI, you can directly access and manipulate system-level functionalities, such as operating system APIs, hardware-related features, etc.

  3. Performance Advantages: Since it can directly call functions from underlying libraries, PHP FFI can improve performance to some extent, especially for applications that need to frequently call system-level functions.

  4. Extensibility: PHP FFI provides flexibility for interacting with external libraries, enabling PHP to better integrate with other languages or systems.

  5. Cross-Platform: Through PHP FFI, you can write cross-platform PHP code without worrying about compatibility issues between different operating systems.

Although the use of PHP FFI requires caution, as directly calling external libraries may lead to security and stability issues, it provides a convenient and powerful solution for specific scenarios that require interaction with C language libraries.

Use Cases

In the past, we could only make PHP code unreadable through encryption and obfuscation, preventing clients from copying and distributing it after delivery, or hardcoding to bypass validation mechanisms. However, with external dynamic link libraries implemented in compiled languages, the cost of cracking can be increased, and the code doesn’t need to be encrypted!

Example

package main

import (
	"C"
	"fmt"
)

func main() {}

//export SayHello
func SayHello(name *C.char) {
	str := C.GoString(name)
	fmt.Printf("Hello %s!\n", str)
}

In Go, receiving a C String requires using the *C.char type, then converting it to a Go string using C.GoString().

go build -o hello.so -buildmode=c-shared

After compilation, it will output two files: hello.so and hello.h:

file hello.so
hello.so: Mach-O 64-bit dynamically linked shared library arm64
/* Code generated by cmd/cgo; DO NOT EDIT. */

/* package _/Users/George/Desktop */


#line 1 "cgo-builtin-export-prolog"

#include <stddef.h>

#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

#endif

/* Start of preamble from import "C" comments.  */




/* End of preamble from import "C" comments.  */


/* Start of boilerplate cgo prologue.  */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef size_t GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
#ifdef _MSC_VER
#include <complex.h>
typedef _Fcomplex GoComplex64;
typedef _Dcomplex GoComplex128;
#else
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
#endif

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif

extern void SayHello(char* name);

#ifdef __cplusplus
}
#endif

In PHP, import the hello.so file through FFI:

<?php
	$ffi = FFI::cdef("void SayHello(char* name);", __DIR__ . "/hello.so");
	$ffi->SayHello("George");
?>

Test if it runs normally:

php hello.php

//Output: Hello George!

I hope this is helpful, Happy hacking…