Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended cpp and go support #2

Merged
merged 3 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ jobs:
mkdir build
cd src/extension
phpize
cd ../lib
go build -buildmode=c-archive -o ../../build/libaikido_go.a
cd ../../build
go build -buildmode=c-archive -o libaikido_go.a ../src/lib/aikido_lib.go
CXX=g++ CXXFLAGS="-fPIC -I../include" LDFLAGS="-L./ -laikido_go" ../src/extension/configure --with-php-config=$HOME/php/bin/php-config
make
make install
Expand Down
5 changes: 3 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ rm -rf build
mkdir build
cd src/extension
phpize
cd ../lib
go build -buildmode=c-archive -o ../../build/libaikido_go.a
cd ../../build
go build -buildmode=c-archive -o libaikido_go.a ../src/lib/aikido_lib.go
CXX=g++ CXXFLAGS="-fPIC -I../include" LDFLAGS="-L./ -laikido_go" ../src/extension/configure
CXX=g++ CXXFLAGS="-fPIC -g -O0 -I../include" LDFLAGS="-L./ -laikido_go" ../src/extension/configure
make
make install
89 changes: 68 additions & 21 deletions src/extension/aikido.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,51 @@
using namespace std;
using json = nlohmann::json;

ZEND_NAMED_FUNCTION(handle_file_get_contents);
ZEND_NAMED_FUNCTION(handle_curl_init);
ZEND_NAMED_FUNCTION(handle_curl_setopt);
ZEND_NAMED_FUNCTION(handle_shell_execution);


struct FUNCTION_HANDLERS {
zif_handler aikido_handler;
zif_handler original_handler;
};

#define AIKIDO_REGISTER_HANDLER(function_name) { #function_name, { handle_##function_name, nullptr } }
/*
Macro for registering an Aikido handler in the HOOKED_FUNCTIONS map.
It takes as parameters the PHP function name to be hooked and C++ function
that should be called when that PHP function is executed.
The nullptr part is a placeholder where the original function handler from
the Zend framework will be stored at initialization when we run the hooking.
*/
#define AIKIDO_REGISTER_HANDLER_EX(function_name, function_pointer) { std::string(#function_name), { function_pointer, nullptr } }
tudor-timcu marked this conversation as resolved.
Show resolved Hide resolved

/*
Shorthand version of AIKIDO_REGISTER_HANDLER_EX that constructs automatically the C++ function to be called.
For example, if function name is curl_init this macro will store { "curl_init", { handle_curl_init, nullptr } }.
*/
#define AIKIDO_REGISTER_HANDLER(function_name) { std::string(#function_name), { handle_##function_name, nullptr } }

unordered_map<const char*, FUNCTION_HANDLERS> HOOKED_FUNCTIONS = {
AIKIDO_REGISTER_HANDLER(file_get_contents),

unordered_map<std::string, FUNCTION_HANDLERS> HOOKED_FUNCTIONS = {
AIKIDO_REGISTER_HANDLER(curl_init),
AIKIDO_REGISTER_HANDLER(curl_setopt)
AIKIDO_REGISTER_HANDLER(curl_setopt),

AIKIDO_REGISTER_HANDLER_EX(exec, handle_shell_execution),
AIKIDO_REGISTER_HANDLER_EX(shell_exec, handle_shell_execution),
AIKIDO_REGISTER_HANDLER_EX(system, handle_shell_execution),
AIKIDO_REGISTER_HANDLER_EX(passthru, handle_shell_execution),
AIKIDO_REGISTER_HANDLER_EX(popen, handle_shell_execution),
AIKIDO_REGISTER_HANDLER_EX(proc_open, handle_shell_execution)
};

#define AIKIDO_HANDLER_START(function_name) php_printf("[AIKIDO-C++] Handler called for \"" #function_name "\"!\n");
#define AIKIDO_HANDLER_END(function_name) HOOKED_FUNCTIONS[#function_name].original_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
#define AIKIDO_GET_FUNCTION_NAME() (ZSTR_VAL(execute_data->func->common.function_name))

ZEND_NAMED_FUNCTION(handle_file_get_contents) {
AIKIDO_HANDLER_START(file_get_contents);
AIKIDO_HANDLER_END(file_get_contents);
}
#define AIKIDO_HANDLER_START() php_printf("[AIKIDO-C++] Handler called for \"%s\"!\n", AIKIDO_GET_FUNCTION_NAME());
#define AIKIDO_HANDLER_END() HOOKED_FUNCTIONS[AIKIDO_GET_FUNCTION_NAME()].original_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);

ZEND_NAMED_FUNCTION(handle_curl_init) {
AIKIDO_HANDLER_START(curl_init);
AIKIDO_HANDLER_START();

zend_string *url = NULL;

Expand All @@ -53,15 +71,15 @@ ZEND_NAMED_FUNCTION(handle_curl_init) {
Z_PARAM_STR_OR_NULL(url)
ZEND_PARSE_PARAMETERS_END();

AIKIDO_HANDLER_END(curl_init);
AIKIDO_HANDLER_END();

if (Z_TYPE_P(return_value) != IS_FALSE) {
// Z_OBJ_P(return_value)
json curl_init_event = {
{ "event", "function_hooked" },
{ "event", "function_executed" },
{ "data", {
{ "function_name", "curl_init" },
{ "parameters", {} }
{ "parameters", json::object() }
} }
};
if (url) {
Expand All @@ -73,7 +91,7 @@ ZEND_NAMED_FUNCTION(handle_curl_init) {
}

ZEND_NAMED_FUNCTION(handle_curl_setopt) {
AIKIDO_HANDLER_START(curl_setopt);
AIKIDO_HANDLER_START();

zval *curlHandle = NULL;
zend_long options = 0;
Expand All @@ -92,11 +110,11 @@ ZEND_NAMED_FUNCTION(handle_curl_setopt) {
std::string urlString(ZSTR_VAL(url));

json curl_setopt_event = {
{ "event", "function_hooked" },
{ "event", "function_executed" },
{ "data", {
{ "function_name", "curl_setopt" },
{ "parameters", {
"url", urlString
{ "url", urlString }
} }
} }
};
Expand All @@ -106,7 +124,36 @@ ZEND_NAMED_FUNCTION(handle_curl_setopt) {
zend_tmp_string_release(tmp_str);
}

AIKIDO_HANDLER_END(curl_setopt);
AIKIDO_HANDLER_END();
}

ZEND_NAMED_FUNCTION(handle_shell_execution) {
tudor-timcu marked this conversation as resolved.
Show resolved Hide resolved
AIKIDO_HANDLER_START();

zend_string *cmd = NULL;

ZEND_PARSE_PARAMETERS_START(1,-1)
Z_PARAM_OPTIONAL
Z_PARAM_STR(cmd)
ZEND_PARSE_PARAMETERS_END();

std::string cmdString(ZSTR_VAL(cmd));

std::string functionNameString(AIKIDO_GET_FUNCTION_NAME());

json shell_execution_event = {
{ "event", "function_executed" },
{ "data", {
{ "function_name", functionNameString },
{ "parameters", {
{ "cmd", cmdString }
} }
} }
};

GoOnEvent(shell_execution_event);

AIKIDO_HANDLER_END();
}

/* For compatibility with older PHP versions */
Expand All @@ -123,11 +170,11 @@ PHP_MINIT_FUNCTION(aikido)
#endif

for ( auto& it : HOOKED_FUNCTIONS ) {
zend_function* function_data = (zend_function*)zend_hash_str_find_ptr(CG(function_table), it.first, strlen(it.first));
zend_function* function_data = (zend_function*)zend_hash_str_find_ptr(CG(function_table), it.first.c_str(), it.first.length());
if (function_data != NULL) {
it.second.original_handler = function_data->internal_function.handler;
function_data->internal_function.handler = it.second.aikido_handler;
php_printf("[AIKIDO-C++] Hooked function \"%s\" using aikido handler %p (original handler %p)!\n", it.first, it.second.aikido_handler, it.second.original_handler);
php_printf("[AIKIDO-C++] Hooked function \"%s\" using aikido handler %p (original handler %p)!\n", it.first.c_str(), it.second.aikido_handler, it.second.original_handler);
}
}

Expand Down
23 changes: 0 additions & 23 deletions src/lib/aikido_lib.go

This file was deleted.

3 changes: 3 additions & 0 deletions src/lib/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module main

go 1.18
24 changes: 24 additions & 0 deletions src/lib/handle_function_executed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

type functionExecutedHandlersFn func(map[string]interface{}) string

var functionExecutedHandlers = map[string]functionExecutedHandlersFn{
"curl_init": OnFunctionExecutedCurl,
"curl_setopt": OnFunctionExecutedCurl,

"exec": OnFunctionExecutedShell,
"shell_exec": OnFunctionExecutedShell,
"system": OnFunctionExecutedShell,
"passthru": OnFunctionExecutedShell,
"popen": OnFunctionExecutedShell,
"proc_open": OnFunctionExecutedShell,
}

func OnFunctionExecuted(data map[string]interface{}) string {
functionName := MustGetFromMap[string](data, "function_name")
parameters := MustGetFromMap[map[string]interface{}](data, "parameters")

CheckIfKeyExists(functionExecutedHandlers, functionName)

return functionExecutedHandlers[functionName](parameters)
}
15 changes: 15 additions & 0 deletions src/lib/handle_shell_execution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import "fmt"

var shellCommands = map[string]bool{}

func OnFunctionExecutedShell(parameters map[string]interface{}) string {
cmd := GetFromMap[string](parameters, "cmd")
if cmd == nil {
return "{}"
}
shellCommands[*cmd] = false
fmt.Println("[AIKIDO-GO] Got shell command:", *cmd)
return "{}"
}
16 changes: 16 additions & 0 deletions src/lib/handle_urls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import "fmt"

var outgoingHostnames = map[string]bool{}

func OnFunctionExecutedCurl(parameters map[string]interface{}) string {
url := GetFromMap[string](parameters, "url")
if url == nil {
return "{}"
}
domain := GetDomain(*url)
outgoingHostnames[domain] = false
fmt.Println("[AIKIDO-GO] Got domain:", domain)
return "{}"
}
40 changes: 40 additions & 0 deletions src/lib/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import "C"
import (
"encoding/json"
"fmt"
)

type eventFunctionExecutedFn func(map[string]interface{}) string

var eventHandlers = map[string]eventFunctionExecutedFn{
"function_executed": OnFunctionExecuted,
}

//export OnEvent
func OnEvent(eventJson string) (outputJson string) {
defer func() {
if r := recover(); r != nil {
fmt.Println("[AIKIDO-GO] Recovered from panic:", r)
outputJson = "{}"
}
}()

fmt.Println("[AIKIDO-GO] OnEvent:", eventJson)

var event map[string]interface{}
err := json.Unmarshal([]byte(eventJson), &event)
if err != nil {
panic(fmt.Sprintf("Error parsing JSON: %s", err))
}

eventName := MustGetFromMap[string](event, "event")
data := MustGetFromMap[map[string]interface{}](event, "data")
tudor-timcu marked this conversation as resolved.
Show resolved Hide resolved

CheckIfKeyExists(eventHandlers, eventName)

return eventHandlers[eventName](data)
}

func main() {}
40 changes: 40 additions & 0 deletions src/lib/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"fmt"
"net/url"
)

func CheckIfKeyExists[K comparable, V any](m map[K]V, key K) {
if _, exists := m[key]; !exists {
panic(fmt.Sprintf("Key %s does not exist in map!", key))
}
}

func GetFromMap[T any](m map[string]interface{}, key string) *T {
value, ok := m[key]
if !ok {
return nil
}
result, ok := value.(T)
if !ok {
return nil
}
return &result
}

func MustGetFromMap[T any](m map[string]interface{}, key string) T {
value := GetFromMap[T](m, key)
if value == nil {
panic(fmt.Sprintf("Error parsing JSON: key %s does not exist or it has an incorrect type", key))
}
return *value
}

func GetDomain(rawurl string) string {
parsedURL, err := url.Parse(rawurl)
if err != nil {
return ""
}
return parsedURL.Hostname()
}
Loading